Skip to content

Porto.sh#46

Closed
Dargon789 wants to merge 5 commits into
Legionfrom
porto.sh
Closed

Porto.sh#46
Dargon789 wants to merge 5 commits into
Legionfrom
porto.sh

Conversation

@Dargon789

@Dargon789 Dargon789 commented Apr 7, 2026

Copy link
Copy Markdown
Owner

Summary by Sourcery

Introduce Merkle tree–based wrapped signature support and extend the account signature format to distinguish Merkle and prehashed signatures.

New Features:

  • Add Merkle tree–based signature verification path allowing digests to be authorized via a signed Merkle root.

Enhancements:

  • Extend wrapped signature encoding to include separate flags for prehashing and Merkle verification.
  • Bump IthacaAccount EIP-712 version identifier to 0.5.11.

Tests:

  • Add fuzz test coverage for Merkle-based signatures, including valid proofs, invalid leaves, tampered proofs, and incorrect roots.
  • Update existing test helpers and gas estimation tests to use the updated wrapped signature format with the new Merkle flag.

@vercel

vercel Bot commented Apr 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
legion Canceled Canceled Apr 7, 2026 11:41am

@sourcery-ai

sourcery-ai Bot commented Apr 7, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds Merkle-tree-based wrapped signature support to IthacaAccount, extends the wrapped signature format with a merkle flag, updates all tests and helpers to the new encoding, and introduces fuzz tests for Merkle proofs, while slightly simplifying EIP-712 domain usage and bumping the account version.

Sequence diagram for wrapped and Merkle signature validation

sequenceDiagram
    actor Caller
    participant IthacaAccount
    participant MerkleProofLib
    participant ECDSA

    Caller->>IthacaAccount: unwrapAndValidateSignature(digest, signature)
    activate IthacaAccount
    IthacaAccount->>IthacaAccount: Check if contract signature
    alt Contract signature
        IthacaAccount->>ECDSA: recoverCalldata(digest, signature)
        ECDSA-->>IthacaAccount: recoveredAddress
        IthacaAccount-->>Caller: isValid, keyHash = 0,1 or 0,0
    else Wrapped signature
        IthacaAccount->>IthacaAccount: Extract keyHash, prehash flag, merkle flag
        alt prehash flag is set
            IthacaAccount->>IthacaAccount: digest = sha2(digest)
        end
        alt merkle flag is set
            IthacaAccount->>IthacaAccount: _verifyMerkleSig(digest, innerSignature)
            activate IthacaAccount
            IthacaAccount->>MerkleProofLib: verifyCalldata(proof, root, digest)
            MerkleProofLib-->>IthacaAccount: proofIsValid
            alt proofIsValid
                IthacaAccount->>IthacaAccount: unwrapAndValidateSignature(root, rootSig)
                IthacaAccount-->>IthacaAccount: isValid, keyHash
                IthacaAccount-->>Caller: isValid, keyHash
            else invalid proof
                IthacaAccount-->>Caller: false, 0
            end
            deactivate IthacaAccount
        else non-merkle wrapped signature
            IthacaAccount->>IthacaAccount: getKey(keyHash)
            IthacaAccount-->>Caller: isValid, keyHash
        end
    end
    deactivate IthacaAccount
Loading

Class diagram for IthacaAccount signature verification changes

classDiagram
    class IthacaAccount {
        +unwrapAndValidateSignature(digest bytes32, signature bytes) bool isValid, bytes32 keyHash
        -_verifyMerkleSig(digest bytes32, signature bytes) bool isValid, bytes32 keyHash
        +getEIP712NameAndVersion() string name, string version
    }

    class MerkleProofLib {
        +verifyCalldata(proof bytes32[], root bytes32, leaf bytes32) bool
    }

    class LibBytes {
        +loadCalldata(data bytes, offset uint256) bytes32
        +truncatedCalldata(data bytes, newLength uint256) bytes
    }

    class EfficientHashLib {
        +sha2(input bytes32) bytes32
    }

    IthacaAccount ..> MerkleProofLib : uses
    IthacaAccount ..> LibBytes : uses
    IthacaAccount ..> EfficientHashLib : uses
Loading

File-Level Changes

Change Details Files
Introduce Merkle-tree-based wrapped signatures and verification in IthacaAccount.
  • Import MerkleProofLib for Merkle proof verification
  • Add internal _verifyMerkleSig that ABI-decodes proof/root/rootSig from calldata via assembly and validates digest membership with MerkleProofLib.verifyCalldata
  • Extend unwrapAndValidateSignature to parse an additional merkle flag byte, route Merkle signatures to _verifyMerkleSig, and keep existing ECDSA and prehash behavior intact
  • Update EIP712 version string from 0.5.10 to 0.5.11 to reflect the new signature scheme
src/IthacaAccount.sol
Extend test helpers and test cases to support the new wrapped signature layout with a merkle flag, and add fuzz coverage for Merkle signatures.
  • Adjust all helper signature constructors in Base.t.sol to append an extra uint8 merkle flag (set to 0 in existing tests) after the prehash flag
  • Update various orchestrator and simulation tests to construct signatures with the extra merkle flag byte
  • Add a new Merkle import and a fuzz test in Account.t.sol that builds a random Merkle tree of digests, creates valid and invalid Merkle-wrapped signatures, and asserts unwrapAndValidateSignature passes/fails appropriately
  • Simplify the eip712Domain destructuring in Account.t.sol now that only verifyingContract is used
test/Base.t.sol
test/Account.t.sol
test/Orchestrator.t.sol
test/SimulateExecute.t.sol
Update ancillary artifacts to stay consistent with the new behavior.
  • Keep forge-std submodule reference in sync (no explicit diff content provided)
  • Regenerate BenchmarkTest snapshot to reflect the new code and gas profiles
lib/forge-std
snapshots/BenchmarkTest.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@snyk-io

snyk-io Bot commented Apr 7, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements Merkle signature verification in IthacaAccount.sol, enabling the validation of digests against a Merkle root signed by an authorized key. The changes include a new _verifyMerkleSig internal function, updates to the signature format to include a merkle flag, and comprehensive fuzz tests. Review feedback identifies a security vulnerability in the assembly block of _verifyMerkleSig due to missing length and offset validations for calldata reads. Additionally, it is recommended to reorder the loading of signature flags in unwrapAndValidateSignature to avoid reading from a truncated slice, ensuring better code robustness.

Comment thread src/IthacaAccount.sol
Comment thread src/IthacaAccount.sol

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The manual ABI decoding in _verifyMerkleSig assumes well‑formed abi.encode(proof, root, rootSig) calldata; consider adding minimal sanity checks on signature.length and the computed offsets to avoid out‑of‑bounds reads on malformed input.
  • In unwrapAndValidateSignature, when merkle is true the previously decoded keyHash is ignored; it may be clearer to either validate that it is zero or explicitly document that keyHash is derived solely from the nested rootSig in the Merkle case.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The manual ABI decoding in `_verifyMerkleSig` assumes well‑formed `abi.encode(proof, root, rootSig)` calldata; consider adding minimal sanity checks on `signature.length` and the computed offsets to avoid out‑of‑bounds reads on malformed input.
- In `unwrapAndValidateSignature`, when `merkle` is true the previously decoded `keyHash` is ignored; it may be clearer to either validate that it is zero or explicitly document that `keyHash` is derived solely from the nested `rootSig` in the Merkle case.

## Individual Comments

### Comment 1
<location path="src/IthacaAccount.sol" line_range="546-554" />
<code_context>
         }

+        bool merkle;
         unchecked {
-            uint256 n = signature.length - 0x21;
+            uint256 n = signature.length - 0x22;
             keyHash = LibBytes.loadCalldata(signature, n);
             signature = LibBytes.truncatedCalldata(signature, n);
             // Do the prehash if last byte is non-zero.
             if (uint256(LibBytes.loadCalldata(signature, n + 1)) & 0xff != 0) {
                 digest = EfficientHashLib.sha2(digest); // `sha256(abi.encode(digest))`.
             }
+            merkle = uint256(LibBytes.loadCalldata(signature, n + 2)) & 0xff != 0;
+        }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** The offsets used for the `prehash` and `merkle` flags appear incorrect relative to the keyHash position.

With `abi.encode(bytes(innerSignature), bytes32(keyHash), bool(prehash), bool(merkle))` and `uint256 n = signature.length - 0x22`, `n` marks the start of the 32‑byte `keyHash`, so the flags must be at `n + 32` and `n + 33` (0x20 / 0x21), not `n + 1` and `n + 2`. As implemented, both flags are read from within the `keyHash` bytes, so the decoded `prehash`/`merkle` values will be wrong. Consider defining `keyHashStart`, `prehashOffset`, and `merkleOffset` constants to make this layout explicit and avoid such off‑by‑N issues.
</issue_to_address>

Fix all in Cursor


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/IthacaAccount.sol
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sequence diagram for wrapped and Merkle signature validation

4 participants